#include "my_chash.h"
#include "my_random.h"

#define MY_CHASH_REAL_SERVER_CHECK_NUM          16


void
my_chash_add_node(my_chash_t *chash, my_str_t *key, void *data, int weight)
{
    if (weight <= 0) {
        return;
    }

    my_chash_node_t *node = my_thread_calloc(sizeof(my_chash_node_t));
    my_chash_node_init(node, key, data, weight);
    my_queue_insert_head(&chash->head, &node->queue);
    chash->nnode++;
}


void
my_chash_build(my_chash_t *chash)
{
    if (!my_rbtree_empty(&chash->tree)) {
        assert(!"repeated build");
    }

    if (chash->nnode > 0) {
        chash->nodes = my_thread_calloc(
            sizeof(my_chash_node_t *) * (size_t)(chash->nnode));
    }

    my_chash_node_t     *node;
    my_chash_vnode_t    *vnode;
    int                 i = 0;
    my_queue_t *q = my_queue_last(&chash->head);
    while(q != &chash->head) {
        node = my_queue_data(q, my_chash_node_t, queue);
        q = q->prev;

        chash->total_weight += node->weight;
        chash->nodes[i] = node;
        i++;

        for (int i = 0; i < chash->replace * node->weight; i++) {
            vnode = my_thread_calloc(sizeof(my_chash_vnode_t));
            my_chash_vnode_init(vnode, node, i);
            my_rbtree_insert(&chash->tree, &vnode->tree_node);
        }
    }
}

static void *
my_chash_get_by_random(my_chash_t *chash)
{
    int64_t rand = my_random_n(chash->total_weight);
    int64_t cur_weight = 0;
    for (int i = 0; i < chash->nnode; i++) {
        cur_weight += chash->nodes[i]->weight;
        if (rand < cur_weight) {
            return chash->nodes[i]->data;
        }
    }

    assert(!"unreachable");
}


// if the must_get parameter is true,
// a random one will be returned if all Nodes are unavailable
void *
my_chash_get(my_chash_t *chash, my_str_t *key,
    my_chash_node_check_pt checker, my_bool_t must_get)
{
    if (chash->nnode <= 0) {
        return NULL;
    }

    my_queue_t *q;
    my_chash_node_t *node;
    my_chash_vnode_t *vnode;
    my_rbtree_node_t *tree_node;

    if (chash->nnode == 1) {
        q = my_queue_last(&chash->head);
        node = my_queue_data(q, my_chash_node_t, queue);

        if (checker && !checker(node)) {
            if (must_get) {
                return node->data;
            }

            return NULL;
        }

        return node->data;
    }

    char buf[64] = { 0 };
    uint32_t crc1, crc2;
    crc1 = my_crc32_short((const u_char *)key->data, key->len);
    int n = snprintf(buf, 64, "#%d-%u-%d#", (int)key->len, crc1, (int)key->len);

    my_crc32_init(crc2);
    my_crc32_update(&crc2, (const u_char *)buf, (size_t)n);
    my_crc32_update(&crc2, (const u_char *)key->data, key->len);
    my_crc32_update(&crc2, (const u_char *)buf, (size_t)n);
    my_crc32_final(crc2);

    my_chash_vnode_t target = {.crc_key = crc2};
    tree_node = my_rbtree_ge(&chash->tree, &target.tree_node);

    if (!tree_node) {
        tree_node = my_rbtree_min(chash->tree.root, chash->tree.sentinel);
    }

    int max_loop = chash->nnode * MY_CHASH_REAL_SERVER_CHECK_NUM;
    for (int i = 0; i < max_loop; i++) {
        vnode = (my_chash_vnode_t *)tree_node;
        if (checker && !checker(vnode->real_node)) {
            tree_node = my_rbtree_next(&chash->tree, tree_node);
            if (tree_node == NULL) {
                tree_node = my_rbtree_min(chash->tree.root, chash->tree.sentinel);
            }
            continue;
        }

        return vnode->real_node->data;
    }

    if (must_get) {
        return my_chash_get_by_random(chash);
    }

    return NULL;
}


my_chash_t *
my_chash_create(int replace)
{
    my_chash_t *chash;

    if (replace < 1) {
        replace = MY_CHASH_DEF_REPLACE;
    }

    chash = my_thread_calloc(sizeof(my_chash_t));
    chash->replace = replace;
    chash->nnode = 0;
    my_queue_init(&chash->head);
    my_rbtree_init(&chash->tree, &chash->sentinel,
        my_chash_vnode_cmp);
    return chash;
}


void
my_chash_destroy(my_chash_t *chash)
{
    my_rbtree_node_t *tree_node;
    my_chash_vnode_t *vnode;

    while(!my_rbtree_empty(&chash->tree)) {
        tree_node = my_rbtree_min(chash->tree.root, chash->tree.sentinel);
        my_rbtree_delete(&chash->tree, tree_node);
        vnode = (my_chash_vnode_t *)tree_node;
        my_thread_free(vnode);
    }

    my_queue_t      *q;
    my_chash_node_t *node;
    while(!my_queue_empty(&chash->head)) {
        q = my_queue_last(&chash->head);
        my_queue_remove(q);
        node = my_queue_data(q, my_chash_node_t, queue);
        my_thread_free(node);
    }

    if (chash->nodes != NULL) {
        my_thread_free(chash->nodes);
    }

    my_thread_free(chash);
}